tree grammar ttcn3tree ;
/*
 * According to symtblOfModules, check the expression semantic; and 
 * generated Java code besed on the define of StringTemplate.
 * NOTE: Check the MODULE's type/function/const/testcase/import defition
 *       is not necessary now.
 */

options 
{
	tokenVocab=ttcn3;
	ASTLabelType=CommonTree;
	output=template;
	k=3;
}

scope gBlockScope {
	Symtbl symtbl;
	
	// for store StringTemplate definitions
	//LinkedList st_localDef;
    //LinkedList st_localStatement;
}

@header{
package org.yatr.yatrc;

import java.util.HashMap;
import java.util.Map;
import java.util.LinkedList;
import java.util.LinkedHashMap;
import java.util.Iterator;
import static org.yatr.yatrc.Symtbl.*;
import static org.yatr.yatrc.Log.*;
import org.yatr.yatrc.TType.*;
import org.yatr.yatrc.*;
}

@members{

String moduleName;
HashMap<String, Symtbl> symtblOfModules;

/* for structed type like record/set start */
List<String> allStructedTypeList;

/* for structed type like record/set end */

/* for function define start */
boolean isInFunctionDef=false;
String  currentFuncName=null;
TType currentFuncReturnType = null;
StringTemplate  currentFuncReturnTypeST=null;
/* for function define end */

/* for statementBlockDef start */
boolean isInStatementBlock=false;
/* for statementBlockDef end */

/* for component start */
String runsOnComp=null;
String runsOnSystem=null;
StringTemplate startTCInstance=null;	//  if component1.start(func()) then  startTCInstance = component1
/* for component end */

/* for control part start */
boolean isInControlPart=false;
/* for control part end */


TType getTypeOfModule(String moduleName, String typeName, Token token, TAttrib tattrib, TSubAttribTYPE tsubattrib){
	/*
	 * Find in MODULE space
	 * TODO: Handle Multi-define in Multi-modules
	 */
	Symtbl symtblOfModule = symtblOfModules.get(moduleName);
	if(symtblOfModule == null) {
	    logError(token, "!!Undefined module name=\%s", moduleName);
	    return null;
	}
	
	TType t = symtblOfModule.findType(typeName, tattrib, tsubattrib);
	/*if(t == null) {
	    logError(token, "!!Undefined type=\%s in module\%s", typeName, moduleName);
	    return null;
	}*/
	return t;
}

TType getRecord(String moduleName, String recordName, Token token){
	TType t = getTypeOfModule(moduleName, recordName, token, TAttrib.TYPE, TSubAttribTYPE.RECORD);
	if(t == null) {
	    logError(token, "!!Undefined record=\%s in module\%s", recordName, moduleName);
	    return null;
	}
	return t;
}

TType getComponent(String moduleName, String componentName, Token token) {
	TType t = getTypeOfModule(moduleName, componentName, token, TAttrib.TYPE, TSubAttribTYPE.COMPONENT);
	if(t == null) {
	    logError(token, "!!Undefined component=\%s in module\%s", componentName, moduleName);
	    return null;
	}
	return t;
}

//get the type define like:   type integer INT;
//the subTypeName and token referenced to INT
TType getSubTypeDef(String moduleName, String subTypeName, Token token) {
	TType t = getTypeOfModule(moduleName, subTypeName, token, TAttrib.TYPE, null);
	if(t == null) {
	    logError(token, "!!Undefined subType name=\%s in module\%s", subTypeName, moduleName);
	    return null;
	}
	return t;
}

/**
 * token: then filed of component componentName
 */
TType getFieldOfComponent(String moduleName, String componentName, Token token){
	//TType t2 = getType(moduleName, token, TAttrib.TYPE, TSubAttribTYPE.COMPONENT);
	/*
	 * Find in MODULE space
	 * TODO: Handle Multi-define in Multi-modules
	 */
	Symtbl symtblOfModule = symtblOfModules.get(moduleName);
	if(symtblOfModule == null) {
	    logError(token, "!!Undefined module name=\%s", moduleName);
	    return null;
	}
	
	//get the component that defined in module	
	TType t2 = symtblOfModule.findType(componentName, TAttrib.TYPE, TSubAttribTYPE.COMPONENT);
	if(t2 == null) {
	    logError(token, "!!Undefined component=\%s in module\%s", componentName, moduleName);
	    return null;
	}
	
	///
	//System.out.println("t2.subtableItem "+token.getText()+":"+ t2.getSubTableItem(token.getText()));
	//System.out.println("t2="+t2);
	return t2.getSubTableItem(token.getText());
}

/**
 * If find token in Symbol table, return true if token redefined, otherwise return false.
 * NOTE: The return TType depended on the TAttrib attribute of token
 */
boolean checkIsDefined(String moduleName, Token token) {
    /* find in local scope */
    TType typeReturn;
    for(int level = $gBlockScope.size()-1; level >=0; --level) {
		Symtbl symtbl = $gBlockScope[level]::symtbl;
		typeReturn = symtbl.findType(token, null, null);
		if(typeReturn != null) {
		    emitRedefinedError(token, typeReturn.getToken());
			return true;
		}
	}
	
	/* find in module scope */
		/*
	 * Find in MODULE space
	 * TODO: Handle Multi-define in Multi-modules
	 */
	Symtbl symtblOfModule = symtblOfModules.get(moduleName);
	if(symtblOfModule == null) {
	    return false;
	}
	typeReturn = symtblOfModule.findType(token, null, null);
	if(typeReturn == null) {
	    return false;
	}
	emitRedefinedError(token, typeReturn.getToken());
    return true;
}

private void emitRedefinedError(Token t1, Token t2) {
    logError(t1, "Token \"\%s\" multi-definitions.", t1.getText());
    if(t2!=null) {
        logError(t2, "This is the previous define.");
    }
}

/**
 * 1. Find token in all of the statementBlocks; if not find, go 2;
 * 2. find it in the symtblOfModules;
 */
TType getType(String moduleName, Token token, TAttrib attrib, TSubAttrib subattrib) {
	TType typeReturn;
	/*
	 * Find in local space
	 */
	for(int level = $gBlockScope.size()-1; level >=0; --level) {
		Symtbl symtbl = $gBlockScope[level]::symtbl;
		typeReturn = symtbl.findType(token, null, null);
		if(typeReturn != null) {
		    if(attrib != null && typeReturn.getAttrib() != attrib) {
		        logError(token, "Expecting type=\%s but \%s", attrib, typeReturn.getAttrib());
		        return null;
		    }
			return typeReturn;
		}
	}
	
	/*
	 * Find in MODULE space
	 * TODO: Handle Multi-define in Multi-modules
	 */
	Symtbl symtblOfModule = symtblOfModules.get(moduleName);
	if(symtblOfModule == null) {
	    logError(null, "Undefined module name=\%s", moduleName);
	    return null;
	}
	typeReturn = symtblOfModule.findType(token, null, null);
	if(typeReturn == null) {
	    logError(token, "Undefined token=\%s", token.getText());
	    return null;
	}
	
    if(attrib != null && typeReturn.getAttrib() != attrib) {
        logError(token, "Expecting type=\%s but \%s", attrib, typeReturn.getAttrib());
        return null;
	}
	
	return typeReturn;
}

/**
 * In boolean expression, we create a new VALUE object represent the BOOLEAN type.
 */
TType doE1E2Bool(Token opToken, TType e1, TType e2, Object... exprExpectTYPEs) {
    TType typeVALUE = doE1E2(opToken, e1, e2, exprExpectTYPEs);
    if(typeVALUE==null) {
        return null;
    }
    TType typeTYPE = newType(opToken, TSubAttribTYPE.BOOLEAN);
    return newValue(opToken, (TSubAttribVALUE)typeVALUE.getSubAttrib(), typeTYPE);
}

//e1 and e2 is TAttrib.VALUE
TType doE1E2(Token opToken, TType e1, TType e2, Object... exprExpectTYPEs) {
    if(e1==null) {   // e2 == null for UNARY_EXPR
        logError(opToken, "e1==null fail.");
        return null;
    }
    
    if(e2 != null && (e1.getAttrib()!=e2.getAttrib())) {
        logError(opToken, "e1.type=\%s != e2.type=\%s", e1.getAttrib(), e2.getAttrib());
        return null;
    }
    if(e1.getAttrib()!= TAttrib.VALUE) {
        logError(opToken, "e1.type=\%s != VALUE ", e1.getAttrib());
        return null;
    }
    
    int i=0;
    for(i=0; i<exprExpectTYPEs.length; ++i) {
        Object o = exprExpectTYPEs[i];
        if(!(o instanceof TSubAttribTYPE)) {
            logError(opToken, "Internal error, unexpected TYPE object");
            throw new RuntimeException("Internal error, unexpected TYPE object");
        }
        if( e1.getType().getSubAttrib() == ((TSubAttribTYPE)o) ) {
            break;
        }
    }
    if(i > 0 && i==exprExpectTYPEs.length) {
        logError(opToken, "Unexception TYPE=\%s for \"\%s\" operator.", e1.getType().getSubAttrib(), opToken.getText());
        return null;
    }
    
    if(e2==null)
        return e1;
    
    /*
     * Check the type compatibility. True if the type is obsolutely equal.
     */
    if(e1.getType().getSubAttrib() != e2.getType().getSubAttrib()) {
        logError(opToken, "e1.datatype=\%s != e2.datatype=\%s", 
            e1.getType().getSubAttrib(), e2.getType().getSubAttrib());
        return null;
    }
    
    if(e1.getSubAttrib() == TSubAttribVALUE.VAR) {
        return e1;
    }
    return e2;
}

/**
 * @param isConstInit true if constDef rule call this method, otherwise false.
 */
void doAssignment(Token token, TType e1, TType e2, boolean isConstInit) {
    if(e1 == null || e2 == null) {
        logError(token, "Internal error: null pointer \%s \%s", 
                                          e1 == null, e2 == null);
    } else {
        if(isConstInit!=true && (e1.getSubAttrib() == TSubAttribVALUE.CONST) ) {
            logError(token, "Constant expression cannot be changed!");
        } else {
            if(e1.getType().getSubAttrib() != e2.getType().getSubAttrib()) {
                logError(token, "Incompatible data type between \%s and \%s.",
                    e1.getType().getSubAttrib(), e2.getType().getSubAttrib());
            }
        }
    }
}

/**
 * Checks the compatibility between formal types and actual types.
 */
boolean checkActualArg(Token token, Map<String, TType> argValueMap, LinkedList<TType> argValueList) {
    if(argValueMap == null) {
        if(argValueList.size() > 0) {
            logError(token, "The formal parameter's size=0 not equal with the actual parameter's size=\%d.", argValueList.size());
            return false;
        }
        return true;
    }
    
    if( argValueMap.size() != argValueList.size()) {
        logError(token, "The formal parameter's size=\%d not equal with the actual parameter's size=\%d.",
            argValueMap.size(), argValueList.size());
        return false;
    }
   
    Iterator<String> iter = argValueMap.keySet().iterator();
    for(int i=0; i<argValueList.size(); ++i) {
        TType formalValue = argValueMap.get(iter.next());
        TType actualValue = argValueList.get(i);
        if(formalValue.getType().getSubAttrib() != actualValue.getType().getSubAttrib()) {
            logError(token, "The data type of argument \%d not equal:\%s vs \%s", i, 
                formalValue.getType().getSubAttrib(), actualValue.getType().getSubAttrib());
            return false;
        }
    }

    return true;
}


}


/**
 * @param symtbl Generated from paser grammar for Module scope symbol table(const/function/type/...).
 */
ttcn3File [HashMap<String, Symtbl> symtbl]
@init{
 symtblOfModules = symtbl;
}
	 : ttcn3Modules
	 {
	    LinkedList<String> importDef=new LinkedList<String>();
        importDef.add("org.etsi.ttcn.tci.*");
        importDef.add("org.etsi.ttcn.tri.*");
        importDef.add("org.yatr.tci.*");
        importDef.add("static org.yatr.comm.Comm.*");
        importDef.add("org.yatr.ttcn.rt.*");
        importDef.add("org.yatr.ttcn.rt.impl.*");
        importDef.add("org.yatr.ttcn.tci.*");
        importDef.add("org.yatr.exception.*");
	 }
	   -> ttcn3File(testSuite={"genttcn"}, imports={importDef}, modules={$ttcn3Modules.st_modules})
	 //| EOF
	 ;

ttcn3Modules returns[List st_modules]
	 : (md+=ttcn3Module)+ {$st_modules = $md;}
	 | EOF
	 ;

ttcn3Module
scope{
LinkedHashMap<Token,StringTemplate> typesDefLinkedMap;
LinkedList<StringTemplate> runnableImpls;
LinkedList<CallableBean> callableList;
}
@init{
    allStructedTypeList = new LinkedList<String>();
	$ttcn3Module::typesDefLinkedMap = new LinkedHashMap<Token,StringTemplate>();
	$ttcn3Module::runnableImpls = new LinkedList<StringTemplate>();
	$ttcn3Module::callableList = new LinkedList<CallableBean>();
}
@after{
    allStructedTypeList = null;
	//$ttcn3Module::typesDefLinkedMap = null;
	//$ttcn3Module::runnableImpls = null;	//save the runnable java code that generated from ".start(func1(arg1,arg2,..))" ttcn3 code.
	//$ttcn3Module::callableList=null;
}
	 : ^( 'module' id {moduleName = $id.text;} 
	      ( moduleDefinitionList )?
	      ( moduleControlPart )?
	    )
	    {
	        if(allStructedTypeList.size()==0) allStructedTypeList = null;
	    }
	    
	    -> ttcn3Module(name={$id.text}, moduleDefList={$moduleDefinitionList.st},
	    		typesDefMap={$ttcn3Module::typesDefLinkedMap},
	    		runnableImpls={$ttcn3Module::runnableImpls},
	            moduleControlPart={$moduleControlPart.st},
	            structedTypeList={allStructedTypeList},
	            callableList={$ttcn3Module::callableList},
	            token={$id.token}, desc={"Module define for \""+moduleName+"\""})
	 ;

moduleControlPart
@init{
isInControlPart=true;
}
@after{
isInControlPart=false;
}
    : ^(c='control' statementBlock[null])
    {
    $ttcn3Module::callableList.add(CallableBean.valueOf(moduleName, "control", "ControlComponent", null));
    }
      -> moduleControlPart(moduleControlStatements={$statementBlock.st}, token={$c.getToken()}, desc={"Control part in module \""+moduleName+"\""})
    ;

moduleDefinitionList
	 : (st+=moduleDefinition)+
	   -> moduleDefinitionList(moduleDefList={$st})
	 ;

moduleDefinition
	 : typeDef      //-> {$typeDef.st}
	   {
	   if($typeDef.t!=null) {
	   		if($typeDef.t.getToken()!=null){
	   			$ttcn3Module::typesDefLinkedMap.put($typeDef.t.getToken(), $typeDef.st);
	   		} else {
	   			logError(null, "typeDef: "+$typeDef.t.toString());
	   		}
	   }
	   }
	 | constDef     -> {$constDef.st}
	 | functionDef  -> {$functionDef.st}
	 | importDef    //-> {$importDef.st}
	 | testcaseDef	-> {$testcaseDef.st}
	 ;

importDef
	 :  ^('import' 'from' id 'all')
	    -> importDef(id={$id.text}, token={$id.token}, desc={$text})
	 ;
typeDef returns[TType t]
	: ^('type' typeDefBody)	 {$t = $typeDefBody.t;} -> {$typeDefBody.st}
	;
typeDefBody returns[TType t]
	: structuredTypeDef	 {$t = $structuredTypeDef.t;} -> {$structuredTypeDef.st}
	| subTypeDef	 {$t = $subTypeDef.t;}	-> {$subTypeDef.st}
	;
structuredTypeDef returns[TType t]
	:	recordDef	 {$t = $recordDef.t;} -> {$recordDef.st}
	| componentDef	 {$t = $componentDef.t;} -> {$componentDef.st}
	;
recordDef returns[TType t]
	:	^('record' structDefBody) { allStructedTypeList.add($structDefBody.idtext); $t = $structDefBody.t; }
		-> typeRecordDef(newname={$structDefBody.idtext}, types={$structDefBody.types}, fields={$structDefBody.fields}, token={$structDefBody.token1}, desc={$text} )
	;
structDefBody returns[List types, List fields, String idtext, Token token1, TType t]
@init{
	$types=new ArrayList();
	$fields=new ArrayList(); 
}
	:	^(id structFieldDef[$types, $fields]+ )
		{
		$idtext=$id.text; $token1=$id.token;
		$t = getRecord(moduleName, $id.text, $id.token);
		}//$types=new ArrayList(); $fields=new ArrayList(); for(int i=0;i<$a.size();i++){$types.add($a.get(i).typename); $fields.add($a.get(i).field);}  }
	;
structFieldDef [List types, List fields]
	:	 ^(id typeName) {$types.add($typeName.st); $fields.add($id.text); }
	;

subTypeDef returns[TType t]
	:	^(id typeName) 
		{
			if($typeName.t.getSubAttrib()==TSubAttribTYPE.RECORD)
				allStructedTypeList.add($id.text);
			$t = getSubTypeDef(moduleName, $id.text, $id.token);
		}
	   	-> subTypeDef(isStruct={$typeName.t.getSubAttrib()==TSubAttribTYPE.RECORD}, oldname={$typeName.st}, newname={$id.text}, token={$id.token}, desc={$text})
	;

componentDef returns[TType t]
	:	^('component' id componentDefList? )
		{
		$t = getComponent(moduleName, $id.text, $id.token);
		}
		-> componentDefine(name={$id.text}, componentDefList={$componentDefList.elementLists}, token={$id.token}, desc={"component define:"+$id.text})
	;
componentDefList returns[List elementLists]
	:	(locals+=componentElementDef)+ {$elementLists = $locals;}
	;
componentElementDef
	:	varInstance	-> {$varInstance.st}
	|	constDef	-> {$constDef.st}
	//| portInstance | timerInstance
	;


typeName returns[TType t]
	 : pt='integer'     {$t = getPredefinedType($pt.text);}
	    ->integerType(name={$pt.text})
	 | pt='charstring'  {$t = getPredefinedType($pt.text);}
	    ->charstringType(name={$pt.text})
	 | pt='boolean'     {$t = getPredefinedType($pt.text);}
	    ->booleanType(name={$pt.text})
	 | pt2=id            {$t = getType(moduleName, $pt2.token, TAttrib.TYPE, null);}
	    ->varRef(id={$pt2.text})
	 ;

constDef
	 : ^( c='const' typeName ( init+=constInitial[$typeName.st] )+ ) 
	    -> constDefine(isInLocalSpace={$gBlockScope.size()!=0}, typename={$typeName.st}, constRefs={$init}, token={$c.getToken()}, desc={$text})
	 ;

constInitial [StringTemplate datatype]
	 : ^(st=':=' id constExpr)
	    {
	        TType idType = getType(moduleName, $id.token, TAttrib.VALUE, null); 
	        doAssignment($st.getToken(), idType, $constExpr.t, true);
	    }
	    -> varOrConstInitial(typeName={$datatype}, left={$id.text}, right={$constExpr.st}, isComponent={false})
	 ;

functionDef
@init{
    isInFunctionDef=true;
    currentFuncName=null;
    currentFuncReturnType=null;
    currentFuncReturnTypeST=null;
    runsOnComp = null;
    runsOnSystem = null;
    Map formalArgs = new LinkedHashMap();
}
@after{
    isInFunctionDef=false;
    currentFuncName=null;
    currentFuncReturnType=null;
    currentFuncReturnTypeST=null;
    runsOnComp = null;
    runsOnSystem = null;
}
	 : ^( 'function' id functionFormalParList[formalArgs]? runsOnSpec? returnType? 
	      {
	        currentFuncName = $id.text;
	        currentFuncReturnTypeST = $returnType.st;
	        FunctionType typeFunc = (FunctionType)getType(moduleName, $id.token, TAttrib.FUNCTION, null);
	        typeFunc.setFormalArgs(formalArgs);
	        
	        if($runsOnSpec.st!=null)
	        	$ttcn3Module::callableList.add(CallableBean.valueOf(moduleName, $id.text, runsOnComp, null));
	        
	      }
	      statementBlock[typeFunc==null?null:typeFunc.getSubTable()] 
	    )
	   -> functionDefine(name={$id.text}, args={formalArgs}, runsOn={$runsOnSpec.st}, returnType={$returnType.st}, 
	                  statements={$statementBlock.st}, token={$id.token}, desc={"Function define in module \""+moduleName+"\""})
	 ;

returnType
	 : ^('return' typeName) {currentFuncReturnType = $typeName.t;}
	   -> {$typeName.st}
	 ;

runsOnSpec
	:	^(RUNS_ON componentType) {runsOnComp=$componentType.text;}      ->	unmodified(text={$componentType.text})
	;

functionFormalParList [Map formalArgs]
	:	^(FORMAL_ARGS (functionFormalPar	{$formalArgs.put($functionFormalPar.token.getText(), $functionFormalPar.typeST);}
	      )+ 
	     )
	;
functionFormalPar	returns[StringTemplate typeST, Token token]
	:	formalValuePar //-> {$formalValuePar.st}
		{
		$typeST = $formalValuePar.typeST;
		$token = $formalValuePar.token;
		}
	;
formalValuePar	returns[StringTemplate typeST, Token token]
	:	 ^(FORMAL_VAR typeName id)	//->formalArg(type={$typeName.st}, name={$id.text})
		{
		$typeST = $typeName.st;
		$token = $id.token;
		}
	;


//@param varDefs  Function arguments or for(var integer i; ...
statementBlock [HashMap<String, TType> varDefs]
scope gBlockScope;
@init{
    isInStatementBlock=true;
    $gBlockScope::symtbl = new Symtbl();
    //$gBlockScope::st_localDef = new LinkedList();
    //$gBlockScope::st_localStatement = new LinkedList();
    
    /* Addding formal arguments to local definition space. */
    if(null != varDefs) {
        for(Iterator<String> iter = varDefs.keySet().iterator(); iter.hasNext();){
            String key = iter.next();
            $gBlockScope::symtbl.addTypeFast(key, varDefs.get(key));
        }
    }
}
@after{
    //System.out.println("deep="+$statementBlock.size());
    isInStatementBlock=false;
}
	 : ^( BLOCK ( functionStatementOrDefList )? )
	    //-> statementBlocks(localDefs={$gBlockScope::st_localDef}, localInstances={$gBlockScope::st_localStatement})
	    -> statementBlocks(localRefs = {$functionStatementOrDefList.localRefs})
	 ;

functionStatementOrDefList returns[List localRefs]
	 : ( locals+=functionStatementOrDef )+ { $localRefs = $locals; }
	 ;

functionStatementOrDef
	 : functionLocalDef    -> {$functionLocalDef.st} //{$gBlockScope::st_localDef.addLast($functionLocalDef.st);}
	 | functionStatement   -> {$functionStatement.st} //{$gBlockScope::st_localStatement.addLast($functionStatement.st);}
	 ;

functionLocalDef
	 : constDef     -> {$constDef.st}
	 | varInstance       -> {$varInstance.st}
	 ;

/**
 * testcase definitions.
 */
testcaseDef
@init{
    runsOnComp = null;
    runsOnSystem = null;
    Map formalArgs = new LinkedHashMap();
}
@after{
    runsOnComp = null;
    runsOnSystem = null;
}
	:	^('testcase' id  '(' testcaseFormalParList[formalArgs]? ')' runsOnSpec systemSpec? 
		{
			TestcaseType typeCase = (TestcaseType)getType(moduleName, $id.token, TAttrib.TESTCASE, null);
	        typeCase.setFormalArgs(formalArgs);
	        
	        $ttcn3Module::callableList.add(CallableBean.valueOf(moduleName, $id.text, runsOnComp, runsOnSystem));
	        
		}
		statementBlock[typeCase.getSubTable()] 
		)
		-> testcaseDefine(name={$id.text}, args={formalArgs}, runsOn={$runsOnSpec.st}, systemOn={$systemSpec.st}, statements={$statementBlock.st}, token={$id.token}, desc={"testcase define:"+$id.text})
	;
systemSpec
	:	^('system' id)	{runsOnSystem=$id.text;}	-> unmodified(text={$id.text})
	;
testcaseFormalParList [Map formalArgs]
	:	^(FORMAL_ARGS (testcaseFormalPar 	{$formalArgs.put($testcaseFormalPar.token.getText(),$testcaseFormalPar.typeST);} )+ )	
	;
testcaseFormalPar	returns[StringTemplate typeST, Token token]
	:	formalValuePar	//-> {$formalValuePar.st}
		{
			$typeST = $formalValuePar.typeST;
			$token = $formalValuePar.token;
		}
	//|	formalTemplatePar
	;


varInstance
	 : ^( v='var' typeName ( vars+=var[$typeName.t, $typeName.st] )+ )
	   -> varDefine(typename={$typeName.st}, varRefs={$vars}, token={$v.getToken()}, desc={$text})
	 ;

//t==TAttrib.TYPE
var [TType typeTYPE, StringTemplate datatype]
@init{
boolean isComponent=false;
}
	 : id  {
	 	 if(isInStatementBlock){
	            if( ! checkIsDefined(moduleName, $id.token) ) {
    	           	 TType typeVALUE = $gBlockScope::symtbl.newValue($id.token, TSubAttribVALUE.VAR, $typeTYPE);
    			 $gBlockScope::symtbl.addType($id.token, typeVALUE);
    		    }
    		 } else {
    		 	//in componentDef
    		 	//do nothing now
    		 }
	       }
	   -> varOrConstInitial(typeName={$datatype}, left={$id.text}, right={null}, isComponent={false})
	 | ^(a=':=' id expr)
	    {
	     if(isInStatementBlock){
	        //check for multi-definition
	        if( ! checkIsDefined(moduleName, $id.token) ) {
	            //
    	        	TType typeVALUE = $gBlockScope::symtbl.newValue($id.token, TSubAttribVALUE.VAR, $typeTYPE);
    			$gBlockScope::symtbl.addType($id.token, typeVALUE);
    			
    			//
    	        	TType idType = getType(moduleName, $id.token, TAttrib.VALUE, null); 
    	        	doAssignment($a.getToken(), idType, $expr.t, false);
    	        	if($expr.t.getType()!=null){
    	        		if($expr.t.getType().getSubAttrib()==TSubAttribTYPE.COMPONENT){
    	        			isComponent=true;
    	        		}
    	        	}
    	        }
    	     } else {
    	     	//in componentDef
    	     	 //do nothing now
    	     }
	    }
	    -> varOrConstInitial(typeName={$datatype}, left={$id.text}, right={$expr.st}, isComponent={isComponent})
	 ;

functionStatement
	 : basicStatements	-> {$basicStatements.st}
	 | behaviourStatements	-> {$behaviourStatements.st}
	 | configurationStatements	-> {$configurationStatements.st}
	 ;
basicStatements
	: assignment               -> {$assignment.st}
	| loopConstruct 	 -> {$loopConstruct.st}
	| conditionalConstruct	-> {$conditionalConstruct.st}
	| logStatement             -> {$logStatement.st}
	;

behaviourStatements
	: functionInstance        -> {$functionInstance.st}
	| testcaseInstance	-> {$testcaseInstance.st}
	| returnStatement          -> {$returnStatement.st}
	;

configurationStatements
	: startTCStatement	//-> {$startTCStatement.st}
	  {
	    TType functype = $startTCStatement.functype;
	  	//save $startTCStatement.st
	  	$ttcn3Module::runnableImpls.add($startTCStatement.st);
	  }
	  -> startTCStatement(threadName={$startTCStatement.name}, modulePrefix={moduleName}, funcName={functype.getName()}, actualArgs={$startTCStatement.actualArgs}, runsOnCompInst={$startTCStatement.runsOnCompInst})
	| connectStatement	-> {$connectStatement.st}
	| disconnectStatement	-> {$disconnectStatement.st}
	| mapStatement		-> {$mapStatement.st}
	| unmapStatement	-> {$unmapStatement.st}
	;

connectStatement
	:	^('connect' singleConnectionSpec)	-> {$singleConnectionSpec.st}
	;
disconnectStatement
	:	^('disconnect' singleOrMultiConnectionSpec)	-> {$singleOrMultiConnectionSpec.st}
	;
singleConnectionSpec
	: ^(SINGLE_CONNECT portRef portRef)
	;
portRef
	: ^(PORT_REF componentRef port)
	;
componentRef
	: componentOrDefaultReference 
	| 'system' 
	| 'self' 
	| 'mtc'
	;
port
	:	portIdentifier
	;
portIdentifier
	:	id
	;
singleOrMultiConnectionSpec
	:	singleConnectionSpec	-> {$singleConnectionSpec.st}
	;

mapStatement
	: ^('map' singleConnectionSpec)	-> {$singleConnectionSpec.st}
	;

unmapStatement
	: ^('unmap' (singleOrMultiConnectionSpec)?) 
	;


/*
 * 1. Generated the code of runnable implemented for 'functionInstance'
 * 2. 
 */
startTCStatement	returns[String name, TType functype, List actualArgs, String runsOnCompInst]
@init{
	startTCInstance = null;
	FunctionType ftype = null;
}
@after{
	startTCInstance = null;
}
	:	^('start' componentOrDefaultReference { startTCInstance = $componentOrDefaultReference.st;} functionInstance)
		{
		ftype=(FunctionType)$functionInstance.functype;
		
		$name = $componentOrDefaultReference.st.toString();
		$functype = ftype;
		$actualArgs = $functionInstance.actualArgs;
		
		String runsOnComp = ftype.getRunsOnComp();
		$runsOnCompInst = startTCInstance.toString();
		}
		//-> startTCStatementRunnable(funcName={ftype.getName()}, formalArgs={ftype.getFormalArgs()}, runsOnComp={runsOnComp})
	;
componentOrDefaultReference	returns[TType t]
	:	variableRef		{$t = $variableRef.t;} -> {$variableRef.st}
	//|	functionInstance -> {$functionInstance.st}
	;

testcaseInstance returns[TType t]
@init{
	String runsOnType=null;
	String runsOnSystemType=null;
	LinkedList<String> formalArgsTypeName=null;
	Map<String, TType> formalArgs=null;
}

	:	^('execute' id 
	 {
	            TType tcase = getType(moduleName, $id.token, TAttrib.TESTCASE, null);
                    if(tcase==null) {
                    	System.out.println("Undefined testcase="+$id.text);
                        return null;
                    }
                    
                    /** start check runs on */
                    TestcaseType tctype = (TestcaseType)tcase;
                    runsOnType = tctype.getRunsOnComp();
                    runsOnSystemType = tctype.getSystemOnComp();
                    /** end */
                    
                /* For check the parameters TYPEs*/
                formalArgs=tcase.getSubTable();
                
                // The testcase's return type is allways the VerdictType
                $t = newValue($id.token, TSubAttribVALUE.VAR, tcase.getType());
	 }
	 testcaseActualParList[formalArgs, $id.token]?
	 {
                    formalArgsTypeName = new LinkedList<String>();
                    if($testcaseActualParList.args!=null&&formalArgs!=null) {
                        Iterator<String> iter=formalArgs.keySet().iterator();
                        for(;iter.hasNext();) {
                            String typename = formalArgs.get(iter.next()).getType().getName();
                            formalArgsTypeName.addLast(typename);
                        }
                    }
	 }
	 )
	 -> testcaseCall(name={$id.text}, args={$testcaseActualParList.args}, argsType={formalArgsTypeName}, runsOnType={runsOnType}, runsOnSystemType={runsOnSystemType}, token={$id.token}, desc={$text})
	;
testcaseActualParList [Map<String, TType> formalArgs, Token token] returns[List args]
	:	actualArgs [$formalArgs, $token] {$args = $actualArgs.args;}
	;

loopConstruct
	 : ^( 'for' initExpr conditionExpr stepExpr statementBlock[null] ) 
	 ;

initExpr
	 : varInstance 
	 | assignment
	 ;

conditionExpr
	 : boolExpr 
	 ;

stepExpr
	 : assignment 
	 ;

conditionalConstruct
	 : ^( 'if' boolExpr statementBlock[null] statementBlock[null]?)
	 ;

logStatement
	 : ^( l='log' ( actualArgs[null, null] )? ) 
	   -> logStatement(args={$actualArgs.args}, token={$l.getToken()}, desc={$text})
	 ;

//check function compatibility between formal parameter and actual parameter
functionInstance returns[TType t, TType functype, List actualArgs, String runsOnInst]	//t = the return type of function
@init{
    LinkedList<String> formalArgsTypeName=null;
    String runsOnInst = null;
    Map<String, TType> formalArgs = null;
}
	 : ^( FUNC_CALL id
	        {
	            TType tfunc = getType(moduleName, $id.token, TAttrib.FUNCTION, null);
                    if(tfunc==null) {
                    	System.out.println("Undefined testcase="+$id.text);
                        return null;
                    }
                    
                    if(startTCInstance!=null){
                    	runsOnInst = startTCInstance.toString();
                    }
                    /** start check runs on */
                    else if(runsOnComp!=null && runsOnComp.equals(((FunctionType)tfunc).getRunsOnComp())) {
                    	runsOnInst = "self";
                    }
                    $runsOnInst = runsOnInst;
                    /** end */
                    
                /* For check the parameters TYPEs*/
                formalArgs=tfunc.getSubTable();
                
                // tfunc.getType() is the function return type
                if(tfunc.getType()!=null) {
                	$t = newValue($id.token, TSubAttribVALUE.VAR, tfunc.getType());
                }
                $functype = tfunc;
            }
            ( actualArgs[formalArgs, $id.token] 
                {
                    formalArgsTypeName = new LinkedList<String>();
                    if($actualArgs.args!=null&&formalArgs!=null) {
                        Iterator<String> iter=formalArgs.keySet().iterator();
                        for(;iter.hasNext();) {
                            String typename = formalArgs.get(iter.next()).getType().getName();
                            formalArgsTypeName.addLast(typename);
                        }
                    }
                    
                    $actualArgs = $actualArgs.args;
                }
            )? 
	    )
	    -> functionCall(name={$id.text}, args={$actualArgs.args}, argsType={formalArgsTypeName}, runsOnInst={runsOnInst},
	    	returnType={"XXX"}, token={$id.token}, desc={$text})
	 ;

actualArgs [Map<String, TType> argsTypes, Token token] returns[List args]
@init{
    LinkedList<TType> argValueList = new LinkedList<TType>();
}
	 : ^( ACTUAL_ARGS ( a+=expr { argValueList.addLast($expr.t); } )+ )
	        {
	        $args=null;
	        if($token!=null) {
                    if(true==checkActualArg($token, $argsTypes, argValueList)) {
                        $args=$a;
                    } else {
                        logError($token, "checkActualArg fail.");
                    }
                } else {    //for log function only.
                    $args=$a;
                }
	        }
	 ;

returnStatement
	 : ^( r='return' ( expr )? )
	    {
	        if(!isInFunctionDef) {
	            logError($r.token, "\"return\" statement must be placed in function definition space.");
	        } else {
	            if($expr.t==null) {
	                if(currentFuncReturnType!=null){
	                    logError($r.token, "Return statement must present a expression.");
	                }
	            }
	            if(currentFuncReturnType==null) {
	                if($expr.t!=null){
	                    logError($r.token, "Return statement cannot present a expression.");
	                }
	            }
	            if(currentFuncReturnType!=null && $expr.t!=null) {
    	            if(currentFuncReturnType.getSubAttrib() != $expr.t.getType().getSubAttrib()) {
    	                logError($r.token, "The returned type of expression is incompatibility with the return type of function \""+currentFuncName+"\".");
    	                logError($r.token, "Expression return type=\%s, function return type=\%s", currentFuncReturnType.getSubAttrib(), $expr.t.getType().getSubAttrib());
    	            }
    	        }
	        }
	    }
	   -> returnStatement(returnType={currentFuncReturnTypeST}, expr={$expr.st} , token={$r.getToken()}, desc={$text})
	 ;

assignment
	 : ^( st=':=' variableRef expr ) 
	    {
	        doAssignment($st.getToken(), $variableRef.t, $expr.t, false);
	    }
	    -> varAssignment(left={$variableRef.st}, right={$expr.st}, token={$st.token}, desc={$text})
	 ;

variableRef returns[TType t]
@init{
	String st="";
}
	 : ^(FIELD_ACCESS id1=id 
	 	{
                    //for component field reference
                    st=$id1.text;
                    if(runsOnComp!=null && null!=($t=getFieldOfComponent(moduleName,runsOnComp, $id1.token))) {
                    	st="self."+st;
                    }/* else if(runsOnSystem!=null && null!=($t=getFieldOfComponent(moduleName, runsOnSystem, $id1.token))){
                    	st="sys."+st;	//Cannot access system component's member fields
                    }*/

		   if($t==null) {
		 	    $t = getType(moduleName, $id1.token, TAttrib.VALUE, null); 
		               if($t!=null) {
	                        if($t.getType().getSubAttrib() != TSubAttribTYPE.RECORD) {
	                            logError($id1.token, "Expecting RECORD type but \%s given", $t.getSubAttrib());
	                            $t = null;
	                        }
	                    }
                    }
                    
	      }
	      ( id2=id 
              {
                if($t!=null) {
                    $t = $t.getType().getSubTableItem($id2.text);
                    if($t==null) {
                        logError($id2.token, "\%s have no field \"\%s\"", $id1.text, $id2.text);
                    }
                }

                //for component field reference
                st=st+"."+$id2.text;
              }
	      )+
	    )
	    -> unmodified(text={st})

	 |  id
	    {
	    	    st=$id.text;
                    if(runsOnComp!=null && null!=($t=getFieldOfComponent(moduleName, runsOnComp, $id.token))) {
                    	st="self."+st;
                    }/* else if(runsOnSystem!=null && null!=($t=getFieldOfComponent(moduleName, runsOnSystem, $id.token))){
                    	st="sys."+st;	//same as above
                    }*/
		    if($t==null) 
		    	$t = getType(moduleName, $id.token, TAttrib.VALUE, null);
	    }
	    -> unmodified(text={st})
	 ;

//extendedFieldRef
//	 :  id + 
//	 ;

constExpr returns[TType t]
	 : expr      {  $t = $expr.t;
	                if($expr.t!=null) {
	                  if($expr.t.getSubAttrib() != TSubAttribVALUE.CONST) {
	                    logError($expr.t.getToken(), "Expecting const expression but \%s(\%s)", 
	                        $expr.t.getSubAttrib(), $expr.text);
	                    $t = null;
	                }
	              }
	            }
	   -> {$expr.st}
	 ;

boolExpr returns[TType t]
	 : expr     { $t = $expr.t;
	              if($expr.t!=null) {
	                if($expr.t.getType().getSubAttrib() != TSubAttribTYPE.BOOLEAN) {
	                    logError($expr.t.getToken(), "Expecting boolean expression but \%s", 
	                        $expr.t.getType().getSubAttrib());
	                    $t = null;
	                }
	              }
	            }
	   -> {$expr.st}
	 ;

// t is a TAttrib.VALUE
expr returns[TType t]
	 : ^(op='==' e1=expr e2=expr)  { $t = doE1E2Bool($op.getToken(), $e1.t, $e2.t); }
	    -> equalOp(left={$e1.st}, right={$e2.st})
	 | ^(op='!=' e1=expr e2=expr)  { $t = doE1E2Bool($op.getToken(), $e1.t, $e2.t); }
	    -> notEqualOp(left={$e1.st}, right={$e2.st})
	 | ^(op='<' e1=expr e2=expr)  { $t = doE1E2Bool($op.getToken(), $e1.t, $e2.t, TSubAttribTYPE.INTEGER/*, TSubAttribTYPE.FLOAT*/ ); }
	    -> lessOp(left={$e1.st}, right={$e2.st})
	 | ^(op='<=' e1=expr e2=expr)  { $t = doE1E2Bool($op.getToken(), $e1.t, $e2.t, TSubAttribTYPE.INTEGER); }
	    -> lessEqualOp(left={$e1.st}, right={$e2.st})
	 | ^(op='>' e1=expr e2=expr)  { $t = doE1E2Bool($op.getToken(), $e1.t, $e2.t, TSubAttribTYPE.INTEGER); }
	    -> greaterOp(left={$e1.st}, right={$e2.st})
	 | ^(op='>=' e1=expr e2=expr)  { $t = doE1E2Bool($op.getToken(), $e1.t, $e2.t, TSubAttribTYPE.INTEGER); }
	    -> greaterEqualOp(left={$e1.st}, right={$e2.st})
	 | ^(op='+' e1=expr e2=expr)  { $t = doE1E2($op.getToken(), $e1.t, $e2.t, TSubAttribTYPE.INTEGER); }
	    -> addOp(left={$e1.st}, right={$e2.st})
	 | ^(op='-' e1=expr e2=expr)  { $t = doE1E2($op.getToken(), $e1.t, $e2.t, TSubAttribTYPE.INTEGER); }
	    -> subOp(left={$e1.st}, right={$e2.st})
	 | ^(op='&' e1=expr e2=expr)  { $t = doE1E2($op.getToken(), $e1.t, $e2.t, TSubAttribTYPE.CHARSTRING); }
	    -> stringOp(left={$e1.st}, right={$e2.st})
	 | ^(op='*' e1=expr e2=expr)  { $t = doE1E2($op.getToken(), $e1.t, $e2.t, TSubAttribTYPE.INTEGER); }
	    -> mulOp(left={$e1.st}, right={$e2.st})
	 | ^(op='/' e1=expr e2=expr)  { $t = doE1E2($op.getToken(), $e1.t, $e2.t, TSubAttribTYPE.INTEGER); }
	    -> divOp(left={$e1.st}, right={$e2.st})
	 | ^(op='mod' e1=expr e2=expr)   { $t = doE1E2($op.getToken(), $e1.t, $e2.t, TSubAttribTYPE.INTEGER); }
	    -> modOp(left={$e1.st}, right={$e2.st})

	 | ^( UNARY_EXPR op='-' e1=expr ) { $t = doE1E2($op.getToken(), $e1.t, null, TSubAttribTYPE.INTEGER); }
	    -> unarySubOp(left={$e1.st})
	 | ^( UNARY_EXPR op='+' e1=expr ) { $t = doE1E2($op.getToken(), $e1.t, null, TSubAttribTYPE.INTEGER); }
	    -> unaryAddOp(left={$e1.st})

	 | value	                    {$t = $value.t;}  -> {$value.st}
	 | opCall  						{$t=$opCall.t;} 	-> {$opCall.st}
	 ;

value returns[TType t]
	 : INT 					        {$t=newValue($INT.getToken(), TSubAttribVALUE.CONST, getPredefinedType("integer"));}
	    ->integerConst(value={$INT.text})
	 | Cstring 					    {$t=newValue($Cstring.getToken(), TSubAttribVALUE.CONST, getPredefinedType("charstring"));}
	    ->charstringConst(value={$Cstring.text})
	 | (bv='true' | bv='false')     {$t=newValue($bv.getToken(), TSubAttribVALUE.CONST, getPredefinedType("boolean"));}
	    ->booleanConst(value={$bv.text})
	 | variableRef					    {$t = $variableRef.t;}
	    -> varRef(id={$variableRef.st})
	 | functionInstance             {$t = $functionInstance.t;}
	    -> {$functionInstance.st}
	 ;

opCall	returns[TType t]
	:	configurationOps	{$t=$configurationOps.t;}	-> {$configurationOps.st}
	;
configurationOps	returns[TType t]
	:	createOp {$t=$createOp.t;}	-> {$createOp.st}
	/*|	'self'
	|	'system'
	|	'mtc'*/
	;
createOp	returns[TType t]
	:	^('create' componentType)
		{ 
			$t = getComponent(moduleName, $componentType.text, $componentType.token);
			if($t!=null){
				$t = newValue($componentType.token, TSubAttribVALUE.VAR, $t);
			}
		}
		->createComponentOp(componentName={$componentType.text}, moduleName={moduleName})
	;

componentType returns[Token token, String text]
	:	id	{$token = $id.token; $text = $id.text;}
	;


//add a postfix
id returns[Token token, String text]
    : ID{$token=$ID.token; 
        $text=$ID.text;//+"_"; 
        $token.setText($ID.text);//+"_");
        }
    ;
